package com.llap.exp190929.service;

import com.llap.exp190929.dto.TransactionDto;
import com.llap.exp190929.entity.CardAccount;
import com.llap.exp190929.entity.TransactionRecord;
import com.llap.exp190929.entity.User;
import com.llap.exp190929.repository.TransactionRecordRepository;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;

@Service
@AllArgsConstructor
@Slf4j
public class TransactionRecordService {
    private TransactionRecordRepository recordRepository;
    private CardAccountService accountService;

    public TransactionRecord findTransactionRecord(int id) {
        return recordRepository.findById(id).orElse(null);
    }

    public Page<TransactionRecord> findTransactionRecords(
            int page, int pageSize,
            String cardNo,
            String filter,
            Date from, Date to) throws Exception {
        if (from != null && to != null) {
            if (to.getTime() < from.getTime()) throw new Exception("From is greater than to");
        }
        Pageable paginator = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Direction.DESC, "transactionDate"));
        Page<TransactionRecord> ret = null;
        CardAccount account = new CardAccount();
        account.setCardNo(cardNo);
        if (from != null && to != null) {
            switch (filter.toLowerCase()) {
                case "all":
                    ret = recordRepository.findAllByAccountAndTransactionDateBetween(account, from, to, paginator);
                    break;
                case "income":
                    ret = recordRepository.findAllByAccountAndTransactionDateBetweenAndIncomeIsNotNull(account, from, to, paginator);
                    break;
                case "expense":
                    ret = recordRepository.findAllByAccountAndTransactionDateBetweenAndExpenseIsNotNull(account, from, to, paginator);
                    break;
                default:
                    throw new Exception("Invalid filter");
            }
        }
        else {
            switch (filter.toLowerCase()) {
                case "all":
                    ret = recordRepository.findAllByAccount(account, paginator);
                    break;
                case "income":
                    ret = recordRepository.findAllByAccountAndIncomeIsNotNull(account, paginator);
                    break;
                case "expense":
                    ret = recordRepository.findAllByAccountAndExpenseIsNotNull(account, paginator);
                    break;
                default:
                    throw new Exception("Invalid filter");
            }
        }
        return ret;
    }

    public Page<TransactionRecord> findTransactionRecords(
            int page, int pageSize,
            String cardNo,
            String filter) throws Exception {
        return findTransactionRecords(page, pageSize, cardNo, filter, null, null);
    }

    public Page<TransactionRecord> findTransactionRecords(int page, int pageSize, String cardNo) throws Exception {
        return findTransactionRecords(page, pageSize, cardNo, "all");
    }

    @Transactional
    public TransactionRecord addTransactionRecord(TransactionDto dto) throws Exception {
        String[] fromArgs = dto.getFromCredential().split(":");
        BigDecimal amount = dto.getAmount();
        if (fromArgs[0].equals(dto.getTo())) {
            throw new Exception("You cannot transfer to yourself");
        }
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new Exception("Transfer amount must be greater than zero");
        }
        if (fromArgs.length != 2) throw new Exception("Invalid credential");
        CardAccount from = accountService.findCardAccount(fromArgs[0]);
        if (from == null) {
            throw new Exception("Cannot find origin card");
        }
        if (!from.getPassword().equals(fromArgs[1])) {
            throw new Exception("Incorrect PIN");
        }
        CardAccount to = accountService.findCardAccount(dto.getTo());
        if (to == null) {
            throw new Exception("Cannot find target card");
        }
        if (to.getStatus() == CardAccount.DISABLED) {
            throw new Exception("Target account has been suspended");
        }

        TransactionRecord fromRecord = new TransactionRecord();
        TransactionRecord toRecord = new TransactionRecord();

        fromRecord.setAccount(from);
        toRecord.setAccount(to);

        fromRecord.setTransactionType("Transfer");
        toRecord.setTransactionType("Transfer");

        fromRecord.setExpense(amount);
        toRecord.setIncome(amount);

        fromRecord.setBalance(from.getBalance().subtract(amount));
        toRecord.setBalance(to.getBalance().add(amount));

        fromRecord.setRemark(dto.getRemark());
        toRecord.setRemark(dto.getRemark());

        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        if (fromRecord.getBalance().compareTo(BigDecimal.ZERO) < 0) {
            throw new Exception("Insufficient balance");
        }

        recordRepository.save(fromRecord);
        accountService.updateCardAccount(from);
        recordRepository.save(toRecord);
        accountService.updateCardAccount(to);

        return fromRecord;
    }

}
